// -------------------------------------------------
// BlinKit - BlinKit Library
// -------------------------------------------------
//   File Name: ElementStyleResources.cpp
// Description: ElementStyleResources Class
//      Author: Ziming Li
//     Created: 2021-07-20
// -------------------------------------------------
// Copyright (C) 2021 MingYang Software Technology.
// -------------------------------------------------

/*
 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
 * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved.
 * Copyright (C) 2013 Google Inc. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 *
 */

#include "./ElementStyleResources.h"

#include "blinkit/blink/renderer/core/CSSPropertyNames.h"
#include "blinkit/blink/renderer/core/css/CSSCursorImageValue.h"
#include "blinkit/blink/renderer/core/css/CSSGradientValue.h"
#include "blinkit/blink/renderer/core/css/CSSImageValue.h"
// BKTODO: #include "core/css/CSSSVGDocumentValue.h"
#include "blinkit/blink/renderer/core/dom/document.h"
#include "blinkit/blink/renderer/core/fetch/resource_fetcher.h"
// BKTODO: #include "core/layout/svg/ReferenceFilterBuilder.h"
#include "blinkit/blink/renderer/core/style/ComputedStyle.h"
#include "blinkit/blink/renderer/core/style/ContentData.h"
#include "blinkit/blink/renderer/core/style/FillLayer.h"
#include "blinkit/blink/renderer/core/style/StyleFetchedImage.h"
#include "blinkit/blink/renderer/core/style/StyleFetchedImageSet.h"
#include "blinkit/blink/renderer/core/style/StyleGeneratedImage.h"
#include "blinkit/blink/renderer/core/style/StyleImage.h"
#include "blinkit/blink/renderer/core/style/StylePendingImage.h"
#include "blinkit/blink/renderer/platform/graphics/filters/FilterOperation.h"

namespace blink {

ElementStyleResources::ElementStyleResources(Document& document, float deviceScaleFactor)
    : m_document(&document)
    , m_deviceScaleFactor(deviceScaleFactor)
{
}

PassRefPtrWillBeRawPtr<StyleImage> ElementStyleResources::styleImage(CSSPropertyID property, const CSSValue& value)
{
    if (value.isImageValue())
        return cachedOrPendingFromValue(property, toCSSImageValue(value));

    if (value.isImageGeneratorValue())
        return generatedOrPendingFromValue(property, toCSSImageGeneratorValue(value));

    if (value.isImageSetValue())
        return setOrPendingFromValue(property, toCSSImageSetValue(value));

    if (value.isCursorImageValue())
        return cursorOrPendingFromValue(property, toCSSCursorImageValue(value));

    return nullptr;
}

PassRefPtrWillBeRawPtr<StyleImage> ElementStyleResources::generatedOrPendingFromValue(CSSPropertyID property, const CSSImageGeneratorValue& value)
{
    if (value.isPending()) {
        m_pendingImageProperties.insert(property);
        return StylePendingImage::create(value);
    }
    return StyleGeneratedImage::create(value);
}

PassRefPtrWillBeRawPtr<StyleImage> ElementStyleResources::setOrPendingFromValue(CSSPropertyID property, const CSSImageSetValue& value)
{
    if (value.isCachePending(m_deviceScaleFactor)) {
        m_pendingImageProperties.insert(property);
        return StylePendingImage::create(value);
    }
    return value.cachedImageSet(m_deviceScaleFactor);
}

PassRefPtrWillBeRawPtr<StyleImage> ElementStyleResources::cachedOrPendingFromValue(CSSPropertyID property, const CSSImageValue& value)
{
    if (value.isCachePending()) {
        m_pendingImageProperties.insert(property);
        return StylePendingImage::create(value);
    }
    value.restoreCachedResourceIfNeeded(*m_document);
    return value.cachedImage();
}

PassRefPtrWillBeRawPtr<StyleImage> ElementStyleResources::cursorOrPendingFromValue(CSSPropertyID property, const CSSCursorImageValue& value)
{
    if (value.isCachePending(m_deviceScaleFactor)) {
        m_pendingImageProperties.insert(property);
        return StylePendingImage::create(value);
    }
    return value.cachedImage(m_deviceScaleFactor);
}

#if 0 // BKTODO:
void ElementStyleResources::addPendingSVGDocument(FilterOperation* filterOperation, CSSSVGDocumentValue* cssSVGDocumentValue)
{
    m_pendingSVGDocuments.set(filterOperation, cssSVGDocumentValue);
}

void ElementStyleResources::loadPendingSVGDocuments(ComputedStyle* computedStyle)
{
    if (!computedStyle->hasFilter() || m_pendingSVGDocuments.isEmpty())
        return;

    FilterOperations::FilterOperationVector& filterOperations = computedStyle->mutableFilter().operations();
    for (unsigned i = 0; i < filterOperations.size(); ++i) {
        RefPtrWillBeRawPtr<FilterOperation> filterOperation = filterOperations.at(i);
        if (filterOperation->type() == FilterOperation::REFERENCE) {
            ReferenceFilterOperation* referenceFilter = toReferenceFilterOperation(filterOperation.get());

            CSSSVGDocumentValue* value = m_pendingSVGDocuments.get(referenceFilter);
            if (!value)
                continue;
            DocumentResource* resource = value->load(m_document);
            if (!resource)
                continue;

            // Stash the DocumentResource on the reference filter.
            ReferenceFilterBuilder::setDocumentResourceReference(referenceFilter, adoptPtr(new DocumentResourceReference(resource)));
        }
    }
}

PassRefPtrWillBeRawPtr<StyleImage> ElementStyleResources::loadPendingImage(StylePendingImage* pendingImage, CrossOriginAttributeValue crossOrigin)
{
    if (CSSImageValue* imageValue = pendingImage->cssImageValue())
        return imageValue->cacheImage(m_document, crossOrigin);

    if (CSSImageGeneratorValue* imageGeneratorValue = pendingImage->cssImageGeneratorValue()) {
        imageGeneratorValue->loadSubimages(m_document);
        return StyleGeneratedImage::create(*imageGeneratorValue);
    }

    if (CSSCursorImageValue* cursorImageValue = pendingImage->cssCursorImageValue())
        return cursorImageValue->cacheImage(m_document, m_deviceScaleFactor);

    if (CSSImageSetValue* imageSetValue = pendingImage->cssImageSetValue())
        return imageSetValue->cacheImageSet(m_document, m_deviceScaleFactor, crossOrigin);

    return nullptr;
}
#endif

void ElementStyleResources::loadPendingImages(ComputedStyle* style)
{
    // We must loop over the properties and then look at the style to see if
    // a pending image exists, and only load that image. For example:
    //
    // <style>
    //    div { background-image: url(a.png); }
    //    div { background-image: url(b.png); }
    //    div { background-image: none; }
    // </style>
    // <div></div>
    //
    // We call styleImage() for both a.png and b.png adding the
    // CSSPropertyBackgroundImage property to the m_pendingImageProperties set,
    // then we null out the background image because of the "none".
    //
    // If we eagerly loaded the images we'd fetch a.png, even though it's not
    // used. If we didn't null check below we'd crash since the none actually
    // removed all background images.

    for (CSSPropertyID property : m_pendingImageProperties) {
        switch (property) {
        case CSSPropertyBackgroundImage: {
            for (FillLayer* backgroundLayer = &style->accessBackgroundLayers(); backgroundLayer; backgroundLayer = backgroundLayer->next()) {
                if (backgroundLayer->image() && backgroundLayer->image()->isPendingImage())
                    ASSERT(false); // BKTODO: backgroundLayer->setImage(loadPendingImage(toStylePendingImage(backgroundLayer->image())));
            }
            break;
        }
        case CSSPropertyContent: {
            for (ContentData* contentData = const_cast<ContentData*>(style->contentData()); contentData; contentData = contentData->next()) {
                if (contentData->isImage()) {
                    StyleImage* image = toImageContentData(contentData)->image();
                    if (image->isPendingImage()) {
                        ASSERT(false); // BKTODO:
#if 0
                        RefPtrWillBeRawPtr<StyleImage> loadedImage = loadPendingImage(toStylePendingImage(image));
                        if (loadedImage)
                            toImageContentData(contentData)->setImage(loadedImage.release());
#endif
                    }
                }
            }
            break;
        }
        case CSSPropertyCursor: {
            if (CursorList* cursorList = style->cursors()) {
                for (size_t i = 0; i < cursorList->size(); ++i) {
                    CursorData& currentCursor = cursorList->at(i);
                    if (StyleImage* image = currentCursor.image()) {
                        if (image->isPendingImage())
                            ASSERT(false); // BKTODO: currentCursor.setImage(loadPendingImage(toStylePendingImage(image)));
                    }
                }
            }
            break;
        }
        case CSSPropertyListStyleImage: {
            if (style->listStyleImage() && style->listStyleImage()->isPendingImage())
                ASSERT(false); // BKTODO: style->setListStyleImage(loadPendingImage(toStylePendingImage(style->listStyleImage())));
            break;
        }
        case CSSPropertyBorderImageSource: {
            if (style->borderImageSource() && style->borderImageSource()->isPendingImage())
                ASSERT(false); // BKTODO: style->setBorderImageSource(loadPendingImage(toStylePendingImage(style->borderImageSource())));
            break;
        }
        case CSSPropertyWebkitBoxReflect: {
            if (StyleReflection* reflection = style->boxReflect()) {
                const NinePieceImage& maskImage = reflection->mask();
                if (maskImage.image() && maskImage.image()->isPendingImage()) {
                    ASSERT(false); // BKTODO:
#if 0
                    RefPtrWillBeRawPtr<StyleImage> loadedImage = loadPendingImage(toStylePendingImage(maskImage.image()));
                    reflection->setMask(NinePieceImage(loadedImage.release(), maskImage.imageSlices(), maskImage.fill(), maskImage.borderSlices(), maskImage.outset(), maskImage.horizontalRule(), maskImage.verticalRule()));
#endif
                }
            }
            break;
        }
        case CSSPropertyWebkitMaskBoxImageSource: {
            if (style->maskBoxImageSource() && style->maskBoxImageSource()->isPendingImage())
                ASSERT(false); // BKTODO: style->setMaskBoxImageSource(loadPendingImage(toStylePendingImage(style->maskBoxImageSource())));
            break;
        }
        case CSSPropertyWebkitMaskImage: {
            for (FillLayer* maskLayer = &style->accessMaskLayers(); maskLayer; maskLayer = maskLayer->next()) {
                if (maskLayer->image() && maskLayer->image()->isPendingImage())
                    ASSERT(false); // BKTODO: maskLayer->setImage(loadPendingImage(toStylePendingImage(maskLayer->image())));
            }
            break;
        }
        case CSSPropertyShapeOutside:
            if (style->shapeOutside() && style->shapeOutside()->image() && style->shapeOutside()->image()->isPendingImage())
                ASSERT(false); // BKTODO: style->shapeOutside()->setImage(loadPendingImage(toStylePendingImage(style->shapeOutside()->image()), CrossOriginAttributeAnonymous));
            break;
        default:
            ASSERT_NOT_REACHED();
        }
    }
}

void ElementStyleResources::loadPendingResources(ComputedStyle* computedStyle)
{
    loadPendingImages(computedStyle);
    // BKTODO: loadPendingSVGDocuments(computedStyle);
}

}
